package com.neulogistics.infrastructure.gateway.filter;


import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.neulogistics.infrastructure.cache.idservice.RedisIdUtil;
import com.neulogistics.infrastructure.common.serviceresult.ErrorCode;
import com.neulogistics.infrastructure.common.serviceresult.ResultUtils;
import com.neulogistics.infrastructure.common.token.JwtTokenUtil;
import com.neulogistics.infrastructure.common.token.LoginUser;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 网关认证过滤器
 */

@Order(1)
@Component
@Slf4j
public class GatewayAuthFilter implements GlobalFilter {
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private RedisIdUtil redisIdUtil;
    @Resource
    private RedissonClient redissonClient;

    //白名单
    private static List<String> whitelist = null;

    //白名单
    private static Map<Integer, String> routeMap = null;

    static {
        //加载白名单和权限路由
        try (
                InputStream whiteListStream = GatewayAuthFilter.class.getResourceAsStream("/security-whitelist.properties");
        ) {
            Properties properties = new Properties();
            properties.load(whiteListStream);
            Set<String> strings = properties.stringPropertyNames();
            whitelist= new ArrayList<>(strings);
        } catch (Exception e) {
            log.error("加载/security-whitelist.properties出错:{}",e.getMessage());
            e.printStackTrace();
        }

        //加载白名单和权限路由
        try (
                InputStream roleRouteStream = GatewayAuthFilter.class.getResourceAsStream("/role-route.properties");
        ) {
            Properties properties = new Properties();
            routeMap = new HashMap<>();
            for (Map.Entry<Object, Object> entry : properties.entrySet()) {
                Integer key = (Integer) entry.getKey();
                String value = (String) entry.getValue();
                routeMap.put(key, value);
            }
        } catch (Exception e) {
            log.error("加载/role-route.properties出错:{}",e.getMessage());
            e.printStackTrace();
        }
    }


    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //请求的url
        String requestUrl = exchange.getRequest().getPath().value();// /userCenter/user/login
        AntPathMatcher pathMatcher = new AntPathMatcher();
        //白名单放行
        for (String url : whitelist) {
            if (pathMatcher.match(url, requestUrl)) {
                return chain.filter(exchange);
            }
        }

        // 对token进行校验
        // 1. 判断有无token，
        String accessToken = getAccessToken(exchange);
        //      没有
        if(StrUtil.isBlank(accessToken)){
            return noAuthHandler("认证过期，请重新登录!", exchange);
        }
        // 2. access token是否合法
        boolean isValid = JwtTokenUtil.verifyToken(accessToken);
        //      不合法，抛出异常，处理异常重新登录
        if(!isValid){
            // token异常，重新登录
            return noAuthHandler("认证过期，请重新登录!", exchange);
        }
        // 3. 校验access token过期
        boolean inTime = JwtTokenUtil.isTokenInTime(accessToken);
        //      解析出用户信息
        JSONObject userJson = JwtTokenUtil.parse(accessToken);
        if(ObjectUtil.isEmpty(userJson)){
            // token异常，重新登录
            return noAuthHandler("认证过期，请重新登录!", exchange);
        }
        LoginUser loginUser = new LoginUser();
        BeanUtil.copyProperties(userJson, loginUser);

        // 没过期，放行
        if(inTime){
            return chain.filter(exchange);
        }
        // 4. 过期，校验refresh token
        String refreshToken = getRefreshToken(exchange);
        //	    不合法 (没有携带、 已经过期、 和缓存中的不一致) ，抛出异常，处理异常重新登录
        String refreshTokenKey = JwtTokenUtil.getRefreshTokenKey(loginUser.userId);
        if(StrUtil.isBlank(refreshToken)
                || Boolean.FALSE.equals(stringRedisTemplate.hasKey(refreshTokenKey))
                || !refreshToken.equals(stringRedisTemplate.opsForValue().get(refreshTokenKey))){
            // token异常，重新登录
            return noAuthHandler("认证过期，请重新登录!", exchange);
        }
        //      合法，刷新两个token，放行（注意并发问题）
        // 上分布式锁
        String lockName = "refreshLock:" + loginUser.userId;
        RLock lock = redissonClient.getLock(lockName);
        if(lock.tryLock()){
            // 双重检查，防止重复更新，不一致说明已经更新完了,不用重复更新，这次放行
            if(!refreshToken.equals(stringRedisTemplate.opsForValue().get(refreshTokenKey))){
                return chain.filter(exchange);
            }
            try {
                String newAccessToken = JwtTokenUtil.generateAccessToken(loginUser);
                String newRefreshToken = redisIdUtil.generateRefreshToken(JwtTokenUtil.REFRESH_TOKEN_ICR_KEY_PREFIX);
                // 向响应头中植入新的token
                ServerHttpResponse response = exchange.getResponse();
                response.getHeaders().add(JwtTokenUtil.ACCESS_HEADER, newAccessToken);
                response.getHeaders().add(JwtTokenUtil.REFRESH_HEADER, newRefreshToken);
                chain.filter(exchange).doOnSuccess(result -> {
                    // 更新缓存
                    stringRedisTemplate.opsForValue().set(refreshTokenKey, newRefreshToken, 7, TimeUnit.DAYS);
                });

            } finally {
                if(lock.isHeldByCurrentThread()){
                    lock.unlock();
                }
            }
        }
        else {
            // 获取失败，先放行也是可以的
            return chain.filter(exchange);
        }
        return null;
    }
    /**
     * 获取token
     */
    private String getAccessToken(ServerWebExchange exchange) {
        String tokenStr = exchange.getRequest().getHeaders().get(JwtTokenUtil.ACCESS_HEADER).get(0);
        if (StrUtil.isBlank(tokenStr)) {
            return null;
        }
//        String token = tokenStr.split("\\.")[1];
//        if (StrUtil.isBlank(token)) {
//            return null;
//        }
        return tokenStr;
    }

    private String getRefreshToken(ServerWebExchange exchange) {
        String tokenStr = exchange.getRequest().getHeaders().get(JwtTokenUtil.REFRESH_HEADER).get(0);
        if (StrUtil.isBlank(tokenStr)) {
            return null;
        }
        return tokenStr;
    }

    private Mono<Void> noAuthHandler(String error, ServerWebExchange exchange) {
        ServerHttpResponse response = exchange.getResponse();
        String jsonResult = JSONUtil.parse(ResultUtils.error(ErrorCode.AUTHENTICATION_ERROR, error)).toString();
        byte[] bits = jsonResult.getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = response.bufferFactory().wrap(bits);
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        return response.writeWith(Mono.just(buffer));
    }

    private boolean checkRoleWithRoute(String url, Integer role){
        if(role == 0){
            return true;
        }

        String route = routeMap.get(role);
        if(StrUtil.isBlank(route)){
            return false;
        }

        return url.startsWith(route);
    }
}
